feat(resilience): add agent immortality foundation [Story 482.1] - #718
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedPull request was closed or merged during review WalkthroughAdds a file-backed Agent Immortality Protocol: constants/schema, safe JSON persistence with file locks and quarantine, normalization and cause diagnosis, legacy/reincarnation-context builders with prevention directives and immunity tokens, per-agent state commits, evolution logging, deterministic reincarnation queue, AutopsyEngine and protocol orchestrator, exports, README/docs, tests, and manifest/registry updates. ChangesAgent Immortality Protocol Foundation
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
📊 Coverage ReportCoverage report not available
Generated by PR Automation (Story 6.1) |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (4)
.aiox-core/core/resilience/agent-immortality.js (3)
119-137: 💤 Low valueCause-of-death heuristic ordering looks intentional — please confirm.
The order matters because
textaggregates name+message+stack and several keywords overlap (e.g., a tool failure whose stack mentionslength/tokenswould classify asCONTEXT_OVERFLOW; a tool error caused by an HTTP timeout would classify asTOOL_EXECUTION_FAILUREbefore reaching the API branch because of thetoolkeyword). The current priorityOVERFLOW > RECURSIVE > TOOL > API > UNKNOWNis defensible, but worth a comment to justify the ordering and prevent future "fixes" that reorder it. Minor: the regex\b(...|enoent|...)matches case-insensitively on the lowercased text, so listingenoent/econnworks — just noting in case anyone later removes.toLowerCase().🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.aiox-core/core/resilience/agent-immortality.js around lines 119 - 137, Add an explicit comment above the diagnoseCause function explaining that the heuristic ordering (CONTEXT_OVERFLOW > RECURSIVE_LOOP > TOOL_EXECUTION_FAILURE > EXTERNAL_API_ERROR > UNKNOWN) is intentional to prioritize context/token/overflow signals over recursive/tool/api signals because keywords can overlap in name/message/stack aggregation; also note that text is lowercased so regex entries like "enoent" and "econn" rely on that and should remain lowercase if the toLowerCase() call is kept. This will prevent future accidental reordering and clarify why TOOL and API checks come after overflow/recursive checks.
52-57: ⚡ Quick win
nextIdcollisions across processes.
idCounteris module-scoped, so two processes started in the same millisecond will produce identicalautopsy-…,state-…, andreincarnation-…ids (both resetidCounterto 0 and shareDate.now()). In a single-process workload this is fine, but in this resilience context multiple agents may crash concurrently. Considercrypto.randomUUID()(Node ≥ 14.17) or appendingprocess.pidto make ids globally unique.♻️ Suggested change
-const fs = require('fs'); -const path = require('path'); -const EventEmitter = require('events'); +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); +const EventEmitter = require('events'); @@ -let idCounter = 0; - function nextId(prefix) { - idCounter += 1; - return `${prefix}-${Date.now().toString(36)}-${idCounter.toString(36)}`; + return `${prefix}-${Date.now().toString(36)}-${crypto.randomUUID().slice(0, 8)}`; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.aiox-core/core/resilience/agent-immortality.js around lines 52 - 57, The current nextId function uses a module-scoped idCounter and Date.now(), causing possible collisions across processes (e.g., autopsy-…, state-…, reincarnation-… ids) when agents start in the same millisecond; update nextId to use a globally-unique source such as crypto.randomUUID() (Node ≥14.17) instead of relying solely on idCounter/Date.now(), and if crypto.randomUUID() is unavailable fall back to appending process.pid and a cryptographic random component (or use crypto.randomBytes) to the generated ID so each process produces unique IDs; update references to nextId/idCounter accordingly.
277-277: 💤 Low valueMinor: simplify reverse-then-find / spread-then-pop.
A few spots can drop the array copy:
- Line 277:
[...commits].reverse().find(c => c.agentId === agentId)→commits.findLast(c => c.agentId === agentId)- Line 299:
[...this.list(agentId)].pop() || null→this.list(agentId).at(-1) || null- Line 463:
[...this.listReports()].pop() || null→this.listReports().at(-1) || null
Array.prototype.findLastandatare ES2022, which the codebase targets per AIOX coding standards.Also applies to: 299-299, 463-463
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.aiox-core/core/resilience/agent-immortality.js at line 277, Replace reverse-then-find and spread-then-pop patterns with modern ES2022 APIs: for the variable using commits and agentId (const previous = [...commits].reverse().find(commit => commit.agentId === agentId)), use commits.findLast(c => c.agentId === agentId); for the call using this.list(agentId) ([...this.list(agentId)].pop() || null) use this.list(agentId).at(-1) || null; and for the call using this.listReports() ([...this.listReports()].pop() || null) use this.listReports().at(-1) || null; these keep behavior the same while removing unnecessary array copies and relying on Array.prototype.findLast and Array.prototype.at.tests/core/resilience/agent-immortality.test.js (1)
44-174: ⚡ Quick winSolid AC coverage — consider adding a concurrency/persistence-rehydration test.
The suite hits every AC end-to-end and the temp-dir harness is clean. Two optional additions worth considering when the race-condition concern raised on the implementation is addressed:
- A test that constructs a fresh
AgentImmortalityProtocolinstance pointing at the sametempDirand callsclaimReincarnation()— verifying the queue actually survives across instance boundaries (the persistence story).- A test that calls
enqueue/claimNextinterleaved across two protocol instances on the sametempDir, asserting no duplicate claim. This will fail today (see the RMW race comment) and will serve as the regression guard once locking is added.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/core/resilience/agent-immortality.test.js` around lines 44 - 174, Add two concurrency/persistence tests: (1) create an AgentImmortalityProtocol pointing at the same tempDir as an existing protocol, call claimReincarnation() (or claimNext via ReincarnationQueue) and assert a previously enqueued item is returned — this verifies the queue rehydrates across instances (reference AgentImmortalityProtocol, ReincarnationQueue, claimReincarnation, claimNext, enqueue). (2) Spawn two distinct AgentImmortalityProtocol/ReincarnationQueue instances pointing at the same tempDir, have one enqueue a report and then concurrently call claimNext from both instances (or interleave enqueue/claimNext) and assert only one instance receives the claim and the other gets null — this will detect RMW race duplication and serve as a regression test for future locking fixes (reference enqueue, claimNext, QueueStatus).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.aiox-core/core/resilience/agent-immortality.js:
- Around line 275-301: The read-modify-write pattern in
StateCommitLog.commitDelta (and related methods list/latest),
ReincarnationQueue.enqueue and ReincarnationQueue.claimNext, and
EvolutionLog.record is racy across processes; wrap each RMW sequence with a
coarse OS-level file lock (e.g., use proper-lockfile or an fs-based sentinel via
fs.openSync(..., 'wx')) so only one process can read, mutate, and write the JSON
atomically; acquire the lock at the start of
commitDelta/enqueue/claimNext/record, re-read the file inside the lock, perform
the mutation (append or state transition and nextId/previousId links), write via
writeJsonAtomic, then release the lock to prevent lost enqueues, double-claims,
and commit forks.
- Around line 529-545: _trackGotcha currently calls gotchasMemory.trackError
directly and can throw, causing captureFailure callers to see failures even
after durable data was written; wrap the call to this.gotchasMemory.trackError
in a try/catch inside _trackGotcha (guarding the call only when
this.gotchasMemory and typeof trackError === 'function') and on error
swallow/log the exception via the existing logger (or a safe fallback) so
failures in the optional gotchas integration do not propagate—target the
_trackGotcha method and the this.gotchasMemory.trackError invocation for this
change.
- Around line 63-69: The readJsonArray function currently calls JSON.parse
directly and can throw a SyntaxError on corrupt files; wrap the parsing (and any
read/trim) in a try/catch inside readJsonArray, catch and log the error with
context (including the filePath and that parsing failed), and return an empty
array on failure so the agent continues operating; ensure the catch only
suppresses JSON/IO parsing errors (rethrow unexpected exceptions if needed) and
keep the function signature and return type unchanged.
In @.aiox-core/core/resilience/README.md:
- Around line 7-29: Replace the local relative
require('./.aiox-core/core/resilience') with the published package path so
consumers can import regardless of working directory; update the import used to
pull AgentImmortalityProtocol and CauseOfDeath to
require('@aiox-squads/core/resilience') (or from the package root
require('@aiox-squads/core') if that re-exports resilience) so the existing
references to AgentImmortalityProtocol and CauseOfDeath continue to work.
---
Nitpick comments:
In @.aiox-core/core/resilience/agent-immortality.js:
- Around line 119-137: Add an explicit comment above the diagnoseCause function
explaining that the heuristic ordering (CONTEXT_OVERFLOW > RECURSIVE_LOOP >
TOOL_EXECUTION_FAILURE > EXTERNAL_API_ERROR > UNKNOWN) is intentional to
prioritize context/token/overflow signals over recursive/tool/api signals
because keywords can overlap in name/message/stack aggregation; also note that
text is lowercased so regex entries like "enoent" and "econn" rely on that and
should remain lowercase if the toLowerCase() call is kept. This will prevent
future accidental reordering and clarify why TOOL and API checks come after
overflow/recursive checks.
- Around line 52-57: The current nextId function uses a module-scoped idCounter
and Date.now(), causing possible collisions across processes (e.g., autopsy-…,
state-…, reincarnation-… ids) when agents start in the same millisecond; update
nextId to use a globally-unique source such as crypto.randomUUID() (Node ≥14.17)
instead of relying solely on idCounter/Date.now(), and if crypto.randomUUID() is
unavailable fall back to appending process.pid and a cryptographic random
component (or use crypto.randomBytes) to the generated ID so each process
produces unique IDs; update references to nextId/idCounter accordingly.
- Line 277: Replace reverse-then-find and spread-then-pop patterns with modern
ES2022 APIs: for the variable using commits and agentId (const previous =
[...commits].reverse().find(commit => commit.agentId === agentId)), use
commits.findLast(c => c.agentId === agentId); for the call using
this.list(agentId) ([...this.list(agentId)].pop() || null) use
this.list(agentId).at(-1) || null; and for the call using this.listReports()
([...this.listReports()].pop() || null) use this.listReports().at(-1) || null;
these keep behavior the same while removing unnecessary array copies and relying
on Array.prototype.findLast and Array.prototype.at.
In `@tests/core/resilience/agent-immortality.test.js`:
- Around line 44-174: Add two concurrency/persistence tests: (1) create an
AgentImmortalityProtocol pointing at the same tempDir as an existing protocol,
call claimReincarnation() (or claimNext via ReincarnationQueue) and assert a
previously enqueued item is returned — this verifies the queue rehydrates across
instances (reference AgentImmortalityProtocol, ReincarnationQueue,
claimReincarnation, claimNext, enqueue). (2) Spawn two distinct
AgentImmortalityProtocol/ReincarnationQueue instances pointing at the same
tempDir, have one enqueue a report and then concurrently call claimNext from
both instances (or interleave enqueue/claimNext) and assert only one instance
receives the claim and the other gets null — this will detect RMW race
duplication and serve as a regression test for future locking fixes (reference
enqueue, claimNext, QueueStatus).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 7a55db0e-c8c0-42c0-bbb4-ff44baa614a9
📒 Files selected for processing (8)
.aiox-core/core/index.js.aiox-core/core/resilience/README.md.aiox-core/core/resilience/agent-immortality.js.aiox-core/core/resilience/index.js.aiox-core/install-manifest.yamldocs/stories/epic-482-agent-immortality/EPIC-482-AGENT-IMMORTALITY.mddocs/stories/epic-482-agent-immortality/STORY-482.1-AUTOPSY-REINCARNATION-FOUNDATION.mdtests/core/resilience/agent-immortality.test.js
c348985 to
2559c38
Compare
2559c38 to
bc7ffe8
Compare
bc7ffe8 to
7c06ac4
Compare
|
🎉 This PR is included in version 5.2.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Summary\n- Implements Agent Immortality Phase 1 for #482: AutopsyEngine, ReincarnationQueue, StateCommitLog, EvolutionLog and AgentImmortalityProtocol orchestration.\n- Persists compact autopsy reports, queued reincarnation context, delta state commits and evolution events under .aiox/immortality.\n- Adds bounded legacy compression and prevention directives so recovery context does not re-trigger context overflow.\n- Adds optional GotchasMemory integration for fatal failure tracking.\n\n## Triage evidence\n- #482 is the last open issue after #483/#717.\n- Historical PR #576 is closed without merge and exposes no merged commits.\n- Historical PR #590 is closed without merge; its large implementation was reviewed as prior art, not copied wholesale.\n- Current main already has CircuitBreaker and GotchasMemory, but no Autopsy/Reincarnation/State Commit/Evolution runtime.\n\n## Validation\n- npm test -- --runTestsByPath tests/core/resilience/agent-immortality.test.js tests/core/resilience-regressions.test.js\n- npx eslint .aiox-core/core/resilience/agent-immortality.js .aiox-core/core/resilience/index.js tests/core/resilience/agent-immortality.test.js\n- npm run generate:manifest && npm run validate:manifest\n- npm run typecheck\n- npm run lint\n\nRefs #482
Summary by CodeRabbit
New Features
Documentation
Tests
Chores